package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;

    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;


    /***
     * 期望单例Bean，找到该类型下多个Bean实例，则抛出异常required a single bean, but 3 were found:
     * 结论：@Autowired首先按类型注入，如果类型有多个Bean实例，然后按BeanID注入
     * 解决方案：
     *   1. 在要使用的对象声明上使用@Primary注解标记
     *   2. 使用 @Autowired 通过注入属性变量名称，指定注入Bean名称
     *   3. 在要使用的对象声明上使用@Qualifier注解标记
     *   4. 使用JDK提供的@Resource(name = "BeanId") 先找名称匹配的Bean，找不到再找类型匹配的Bean
     */
    //@Autowired
    //@Qualifier("threadPoolTaskExecutor")
    @Resource(name = "threadPoolTaskExecutor")
    private Executor executor;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 将指定专辑保存到ES索引库
     *
     * @param albumId
     */
    @Override
    public void upperAlbum(Long albumId) {
        //1.创建专辑索引库文档对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        //2.远程调用专辑服务：根据专辑ID查询专辑信息、（包含标签列表）封装专辑基本信息跟标签列表
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //2.1 远程调用专辑服务获取专辑信息跟标签列表
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumInfo, "专辑{}不存在", albumId);
            //2.2 采用对象拷贝将专辑基本信息赋值给专辑索引库文档对象
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);
            //2.3 获取专辑标签列表，单独处理标签列表
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollUtil.isNotEmpty(albumAttributeValueVoList)) {
                //将AlbumAttributeValue转为AttributeValueIndex
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList
                        .stream()
                        .map(albumAttributeValue -> BeanUtil.copyProperties(albumAttributeValue, AttributeValueIndex.class))
                        .collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, executor);

        //3.远程调用专辑服务：根据专辑所属三级分类ID查询分类信息，封装专辑1,2级分类ID
        CompletableFuture<Void> categoryCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            BaseCategoryView baseCategoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(baseCategoryView, "分类{}不存在", albumInfo.getCategory3Id());
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, executor);

        //4.远程调用用户服务：根据专辑所属主播ID查询主播信息，封装专辑主播名称
        CompletableFuture<Void> announcerCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
            Assert.notNull(userInfoVo, "用户{}不存在", albumInfo.getUserId());
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, executor);


        //5.随机为当前专辑文档生成统计数值，基于统计数值按指定规则生成热度值 TODO 最终一定要远程获取真实专辑统计数值
        CompletableFuture<Void> statCompletableFuture = CompletableFuture.runAsync(() -> {
            //5.1 生成统计数值
            int num1 = RandomUtil.randomInt(5000);
            int num2 = RandomUtil.randomInt(3000);
            int num3 = RandomUtil.randomInt(2000);
            int num4 = RandomUtil.randomInt(1000);
            albumInfoIndex.setPlayStatNum(num1);
            albumInfoIndex.setSubscribeStatNum(num2);
            albumInfoIndex.setBuyStatNum(num3);
            albumInfoIndex.setCommentStatNum(num4);
            //5.2 基于统计数值生成热度值 规则：累加四项 统计值*系数
            BigDecimal hotScore = BigDecimal.valueOf(num1).multiply(new BigDecimal("0.1"))
                    .add(BigDecimal.valueOf(num2).multiply(new BigDecimal("0.2")))
                    .add(BigDecimal.valueOf(num3).multiply(new BigDecimal("0.3")))
                    .add(BigDecimal.valueOf(num4).multiply(new BigDecimal("0.4")));
            albumInfoIndex.setHotScore(hotScore.doubleValue());
        }, executor);

        //6.组合所有异步任务
        CompletableFuture.allOf(
                        albumInfoCompletableFuture,
                        categoryCompletableFuture,
                        announcerCompletableFuture,
                        statCompletableFuture
                )
                .orTimeout(1, TimeUnit.SECONDS)
                .join();

        //7.将专辑文档保存到专辑索引库
        albumInfoIndexRepository.save(albumInfoIndex);

        //8.将专辑名称存入提示词索引库
        this.saveSuggestInfo(albumInfoIndex);


        //9.将专辑ID存入布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        if(bloomFilter.isExists()){
            bloomFilter.add(albumId);
        }
    }

    /**
     * 从索引库删除专辑文档
     *
     * @param albumId
     * @return
     */
    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoIndexRepository.deleteById(albumId);
        suggestIndexRepository.deleteById(albumId.toString());
    }

    @Autowired
    private ElasticsearchClient elasticsearchClient;


    /**
     * 站内搜索
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            //一、构建完整检索请求对象：包含完整DSL语句请求体参数
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            System.err.println("本次检索DSL：");
            System.out.println(searchRequest);
            //二、执行检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

            //三、解析ES响应结果
            return this.parseResult(searchResponse, albumIndexQuery);
        } catch (IOException e) {
            log.error("执行检索失败", e);
            throw new RuntimeException(e);
        }
    }

    private static final String INDEX_NAME = "albuminfo";

    /**
     * 整体看采用传统方式，局部采用Lambda简化开发
     * 基于查询条件封装ES检索DSL语句
     *
     * @param albumIndexQuery 查询条件
     * @return
     */
    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //1.创建检索构建器对象 封装 请求地址中：index 索引库名称
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(INDEX_NAME);
        //2.设置查询条件：请求体参数query
        String keyword = albumIndexQuery.getKeyword();
        //2.0 创建bool查询条件：用于封装所有查询条件
        BoolQuery.Builder allConditionBoolQueryBuilder = new BoolQuery.Builder();
        //2.1 条件1：关键字 采用must 有算分机制按相关性返回
        if (StringUtils.isNotBlank(keyword)) {
            allConditionBoolQueryBuilder.must(m -> m.match(m1 -> m1.field("albumTitle").query(keyword)));
        }
        //2.2 条件2：一二三级分类ID 采用filter 有缓存机制
        //2.2.1. 封装1级分类ID查询条件
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        //2.2.2. 封装2级分类ID查询条件
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        //2.2.3. 封装3级分类ID查询条件
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            allConditionBoolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }
        //2.3 条件3：多组标签List集合 每组标签形式=标签ID：标签值ID
        List<String> attributeList = albumIndexQuery.getAttributeList();
        //2.4 每遍历一组标签过滤条件，设置一个Nested查询
        if (CollUtil.isNotEmpty(attributeList)) {
            for (String s : attributeList) {
                String[] split = s.split(":");
                if (split != null && split.length == 2) {
                    allConditionBoolQueryBuilder
                            .filter(f -> f.nested(
                                    n -> n.path("attributeValueIndexList")
                                            .query(q -> q.bool(b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                                    .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                                            ))
                            ));
                }
            }
        }

        builder.query(allConditionBoolQueryBuilder.build()._toQuery());
        //builder.query();
        //3.设置分页：请求体参数from size
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        int from = (pageNo - 1) * pageSize;
        builder.from(from).size(pageSize);

        //4.设置排序：请求体参数sort
        //4.1 前后端约定 如果需要排序提交参数order值=排序字段（1,2,3）：排序方式（asc,desc）
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            //4.2 获取排序字段以及排序方式
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                String sortField = "";
                switch (split[0]) {
                    case "1":
                        sortField = "hotScore";
                        break;
                    case "2":
                        sortField = "playStatNum";
                        break;
                    case "3":
                        sortField = "createTime";
                        break;
                }
                String direction = split[1];
                //4.3 动态设置排序
                SortOrder sortOrder = "asc".equals(direction) ? SortOrder.Asc : SortOrder.Desc;
                String finalSortField = sortField;
                builder.sort(s -> s.field(f -> f.field(finalSortField).order(sortOrder)));
            }
        }

        //5.设置高亮：请求体参数highlight 前提条件:用户采用关键字全文查询text字段
        if (StringUtils.isNotBlank(keyword)) {
            builder.highlight(h -> h.fields("albumTitle", f -> f.preTags("<font style='color:red'>").postTags("</font>")));
        }

        //6.设置响应字段：请求体参数_source
        builder.source(s -> s.filter(f -> f.excludes("attributeValueIndexList",
                "hotScore",
                "commentStatNum",
                "buyStatNum",
                "subscribeStatNum",
                "announcerName")));

        //7.基于构建器对象返回实际检索对象
        return builder.build();
    }


    /**
     * 解析ES响应结果
     *
     * @param searchResponse  ES响应对象
     * @param albumIndexQuery 查询条件
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        //1.创建响应结果对象
        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();
        //2.封装VO中分页四项信息
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        //2.1 获取ES结果中总记录数
        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();
        long total = hits.total().value();
        //2.2 计算总页数  页大小=10  总计：101
        long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        vo.setPageNo(pageNo);
        vo.setPageSize(pageSize);
        vo.setTotal(total);
        vo.setTotalPages(totalPages);

        //3.封装Vo中专辑列表
        List<Hit<AlbumInfoIndex>> hitsList = hits.hits();
        if (CollUtil.isNotEmpty(hitsList)) {
            List<AlbumInfoIndexVo> infoIndexVoList = hitsList
                    .stream()
                    .map(hit -> {
                        //3.1 获取命中文档原数据
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        //3.2 处理高亮片段
                        Map<String, List<String>> highlight = hit.highlight();
                        if (CollUtil.isNotEmpty(highlight)) {
                            String albumTitleHightLight = highlight.get("albumTitle").get(0);
                            albumInfoIndex.setAlbumTitle(albumTitleHightLight);
                        }
                        //3.3响应VO
                        return BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
                    }).collect(Collectors.toList());
            vo.setList(infoIndexVoList);
        }
        //x.响应结果VO对象
        return vo;
    }

    /**
     * 查询1级分类下置顶3级分类热度TOP6专辑
     *
     * @param category1Id
     * @return [{"baseCategory3":{三级分类对象},list:[专辑列表]},,{其他6个置顶分类热门专辑Map}]
     */
    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        try {
            //1.根据1级分类ID获取置顶三级分类ID
            //1.1 远程调用远程调用专辑微服务获取置顶3级分类
            List<BaseCategory3> baseCategory3List = albumFeignClient.findTopBaseCategory3(category1Id).getData();
            Assert.notNull(baseCategory3List, "一级分类{}置顶三级分类不存在", category1Id);

            //1.2 获取置顶三级分类ID列表
            List<FieldValue> fieldValueList = baseCategory3List
                    .stream()
                    .map(c3 -> FieldValue.of(c3.getId()))
                    .collect(Collectors.toList());

            //1.3 为了封装结果Map中分类对象，将三级分类List转为Map<3级分类ID，3级分类对象>
            Map<Long, BaseCategory3> category3Map = baseCategory3List
                    .stream()
                    .collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));

            //2. 调用ES进行检索：置顶分类热门专辑
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index(INDEX_NAME)
                            .size(0)
                            .query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                            .aggregations(
                                    "c3_agg", a -> a.terms(t -> t.field("category3Id").size(7))
                                            .aggregations("top6", a1 -> a1.topHits(
                                                    t -> t.size(6)
                                                            .sort(s1 -> s1.field(f -> f.field("hotScore").order(SortOrder.Desc)))
                                                            .source(s1 -> s1.filter(f -> f.excludes("attributeValueIndexList",
                                                                    "hotScore",
                                                                    "commentStatNum",
                                                                    "buyStatNum",
                                                                    "subscribeStatNum",
                                                                    "announcerName")))
                                            ))
                            )
                    , AlbumInfoIndex.class);

            //3.解析聚合结果，获取置顶分类包含热度TOP6专辑列表 将7个置顶分类热门专辑Map封装到List集合中
            List<LongTermsBucket> longTermsBuckets = searchResponse.aggregations().get("c3_agg").lterms().buckets().array();
            //3.1 遍历三级分类聚合结果，每遍历一次就封装一个置顶分类热门专辑Map对象
            if (CollUtil.isNotEmpty(longTermsBuckets)) {
                List<Map<String, Object>> topCategory3MapList = longTermsBuckets
                        .stream()
                        .map(bucket -> {
                            //3.1.1 创建Map封装置顶分类热门专辑
                            Map<String, Object> map = new HashMap<>();
                            //3.1.2 获取聚合结果中三级分类ID
                            long topCategory3Id = bucket.key();
                            //3.1.3 获取top6专辑列表
                            //获取三级分类聚合内子聚合"top6"
                            List<Hit<JsonData>> top6HitsList = bucket.aggregations().get("top6")
                                    .topHits().hits().hits();
                            if (CollUtil.isNotEmpty(top6HitsList)) {
                                //遍历Hit集合 将集合泛型从Hit转为AlbumInfoIndex类型
                                List<AlbumInfoIndex> topAlbumList = top6HitsList.stream()
                                        .map(hit -> {
                                            //获取专辑JSON字符串
                                            String albumInfoIndexJSONStr = hit.source().toString();
                                            //将专辑JSON字符串转为AlbumInfoIndex对象
                                            AlbumInfoIndex albumInfoIndex = JSON.parseObject(albumInfoIndexJSONStr, AlbumInfoIndex.class);
                                            return albumInfoIndex;
                                        }).collect(Collectors.toList());
                                //3.2 封装置顶分类热门专辑Map
                                map.put("baseCategory3", category3Map.get(topCategory3Id));
                                map.put("list", topAlbumList);
                            }
                            return map;
                        }).collect(Collectors.toList());
                return topCategory3MapList;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    /**
     * 保存提示词文档到提示词索引库
     *
     * @param albumInfoIndex
     */
    @Override
    public void saveSuggestInfo(AlbumInfoIndex albumInfoIndex) {
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfoIndex.getId().toString());
        String albumTitle = albumInfoIndex.getAlbumTitle();
        suggestIndex.setTitle(albumTitle);
        //用于汉字自动补全字段
        suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
        //用于拼音自动补全字段
        String pinyin = PinyinUtil.getPinyin(albumTitle, "");
        suggestIndex.setKeywordPinyin(new Completion(new String[]{pinyin}));
        //用于拼音首字母自动补全字段
        String firstLetter = PinyinUtil.getFirstLetter(albumTitle, "");
        suggestIndex.setKeywordSequence(new Completion(new String[]{firstLetter}));
        suggestIndexRepository.save(suggestIndex);
    }

    /**
     * 根据用户已录入字符（汉字or拼音or首字母）自动提示
     *
     * @param keyword 关键字（汉字or拼音or首字母）
     * @return 自动补全列表长度最多10个
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            //1.优先根据录入字符进行建议词检索解析检索结果
            //1.1 执行检索
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index("suggestinfo")
                            .suggest(
                                    s1 -> s1.suggesters("letter-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordSequence").size(10).skipDuplicates(true)))
                                            .suggesters("pinyin-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordPinyin").size(10).skipDuplicates(true)))
                                            .suggesters("keyword-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keyword").size(10).skipDuplicates(true)))
                            )
                    ,
                    SuggestIndex.class);
            //1.2 解析建议词结果 避免提示词列表有重复项
            Set<String> suggestSet = new HashSet<>();
            suggestSet.addAll(this.parseSuggestResult(searchResponse, "letter-suggest"));
            suggestSet.addAll(this.parseSuggestResult(searchResponse, "pinyin-suggest"));
            suggestSet.addAll(this.parseSuggestResult(searchResponse, "keyword-suggest"));

            //2.如果建议词检索结果不足10个，尝试采用全文查询补全到到10个
            int size = suggestSet.size();
            if (size < 10) {
                SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(
                        s -> s.index(INDEX_NAME)
                                .query(q -> q.match(m -> m.field("albumTitle").query(keyword)))
                                .size(10)
                                .source(s1 -> s1.filter(f -> f.includes("albumTitle")))
                        ,
                        AlbumInfoIndex.class
                );
                List<Hit<AlbumInfoIndex>> hitList = response.hits().hits();
                if (CollUtil.isNotEmpty(hitList)) {
                    for (Hit<AlbumInfoIndex> hit : hitList) {
                        String albumTitle = hit.source().getAlbumTitle();
                        suggestSet.add(albumTitle);
                        if (suggestSet.size() >= 10) {
                            break;
                        }
                    }
                }
            }
            if (suggestSet.size() >= 10) {
                return new ArrayList<>(suggestSet).subList(0, 10);
            }
            return new ArrayList<>(suggestSet);
        } catch (IOException e) {
            log.error("自动补全失败！");
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析提示词结果
     *
     * @param searchResponse 检索结果
     * @param suggestName    自定义建议参数名称
     * @return
     */
    @Override
    public Collection<String> parseSuggestResult(SearchResponse<SuggestIndex> searchResponse, String suggestName) {
        List<String> suggestResultlist = new ArrayList<>();
        List<Suggestion<SuggestIndex>> suggestions = searchResponse.suggest().get(suggestName);
        for (Suggestion<SuggestIndex> suggestion : suggestions) {
            for (CompletionSuggestOption<SuggestIndex> option : suggestion.completion().options()) {
                SuggestIndex suggestIndex = option.source();
                String title = suggestIndex.getTitle();
                suggestResultlist.add(title);
            }
        }
        return suggestResultlist;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 更新Redis小时榜
     *
     * @return
     */
    @Override
    public void updateLatelyAlbumRanking() {
        try {
            //1.检索ES得到响应的TOP20专辑列表
            //1.1 远程调用专辑服务获取一级分类ID列表
            List<BaseCategory1> baseCategory1List = albumFeignClient.getAllCategory1().getData();
            Assert.notNull(baseCategory1List, "一级分类列表为空！");
            List<Long> category1IdList = baseCategory1List
                    .stream()
                    .map(BaseCategory1::getId)
                    .collect(Collectors.toList());

            //1.2 循环遍历1级分类ID-构建DSL检索 条件：一级分类ID（共计15个）+排序（五种排序方式）+长度（前20）
            for (Long category1Id : category1IdList) {
                //定义Redis中小时榜hash的Key
                String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                //创建绑定hash对象方便操作hash结构
                BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(redisKey);
                //处理某个一级分类排行数据，遍历5种不同排序字段
                String[] rankingDimensionArray
                        = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
                for (String rankingDimension : rankingDimensionArray) {
                    SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                            s -> s.index(INDEX_NAME).size(20)
                                    .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                    .sort(s1 -> s1.field(f -> f.field(rankingDimension).order(SortOrder.Desc)))
                            , AlbumInfoIndex.class);
                    //2.解析ES检索结果，将专辑列表保存到Redis中Hash中
                    List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
                    if (CollUtil.isNotEmpty(hitList)) {
                        List<AlbumInfoIndex> top20List = hitList.stream().map(hit -> {
                            AlbumInfoIndex source = hit.source();
                            return source;
                        }).collect(Collectors.toList());
                        hashOps.put(rankingDimension, top20List);
                    }
                }
            }
        } catch (IOException e) {
            log.error("更新Redis小时榜失败！");
            throw new RuntimeException(e);
        }
    }

    /**
     * 查询Redis中不同分类下不同维度TOP20专辑列表
     *
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
        //定义Redis中小时榜hash的Key
        String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        String field = dimension;
        //创建绑定hash对象方便操作hash结构
        BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(redisKey);
        Boolean flag = hashOps.hasKey(field);
        if (flag) {
            List<AlbumInfoIndex> list = hashOps.get(field);
            List<AlbumInfoIndexVo> albumInfoIndexVoList = list
                    .stream()
                    .map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class))
                    .collect(Collectors.toList());
            return albumInfoIndexVoList;
        }
        return null;
    }

    /**
     #增量更新
     POST /albuminfo/_update/1
     {
         "script": {
             "source": "ctx._source.playStatNum += params.increment",
             "lang": "painless",
             "params": {
                 "increment": 1
             }
         }
     }
     */
    /**
     * 更新专辑统计数值
     *
     * @param mqVo
     */
    @Override
    public void updateAlbumStat(TrackStatMqVo mqVo) {
        try {
            Long albumId = mqVo.getAlbumId();
            String incrementField = "";
            if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) {
                incrementField = "playStatNum";

            } else if (SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())) {
                incrementField = "commentStatNum";
            }
            String finalIncrementField = incrementField;
            elasticsearchClient.update(
                    u -> u.index(INDEX_NAME).id(albumId.toString())
                            .script(s -> s.inline(i -> i.source("ctx._source." + finalIncrementField + " += params.increment").lang("painless").params(Map.of("increment", JsonData.of(mqVo.getCount()))))),
                    AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


}
